<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>Golang实现命令行菜单CLI效果 | 双能卫</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="在Unix-Like系统中，CLI(Command Line Interface)通常都是通过传统的getopt来实现的，在Python中除了getopt外还有更高级的optparse以及argparse。在Golang中，系统库flag为我们提供了CLI的支持，当然还有一些更高级的封装实现，如codegangsta/cli,docker增强的flag。但是对于平民百姓的一般使用，用官方的flag">
<meta property="og:type" content="article">
<meta property="og:title" content="Golang实现命令行菜单CLI效果">
<meta property="og:url" content="http://www.libcz.com/blog/2015/03/05/golang/standard_library/flag/golang实现命令行菜单cli效果/index.html">
<meta property="og:site_name" content="双能卫">
<meta property="og:description" content="在Unix-Like系统中，CLI(Command Line Interface)通常都是通过传统的getopt来实现的，在Python中除了getopt外还有更高级的optparse以及argparse。在Golang中，系统库flag为我们提供了CLI的支持，当然还有一些更高级的封装实现，如codegangsta/cli,docker增强的flag。但是对于平民百姓的一般使用，用官方的flag">
<meta property="og:updated_time" content="2015-10-14T06:56:25.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Golang实现命令行菜单CLI效果">
<meta name="twitter:description" content="在Unix-Like系统中，CLI(Command Line Interface)通常都是通过传统的getopt来实现的，在Python中除了getopt外还有更高级的optparse以及argparse。在Golang中，系统库flag为我们提供了CLI的支持，当然还有一些更高级的封装实现，如codegangsta/cli,docker增强的flag。但是对于平民百姓的一般使用，用官方的flag">
  
    <link rel="alternative" href="/blog/atom.xml" title="双能卫" type="application/atom+xml">
  
  
    <link rel="icon" href="http://images.libcz.com:8000/images/favicon.png">
  
  <link rel="stylesheet" href="/blog/css/style.css" type="text/css">
</head>
<body>
  <div id="container">
    <div class="left-col">
    <div class="overlay"></div>
<div class="intrude-less">
	<header id="header" class="inner">
		<a href="/" class="profilepic">
			
			<img lazy-src="http://images.libcz.com:8000/images/white_bg_logo.png" class="js-avatar">
			
		</a>

		<hgroup>
		  <h1 class="header-author"><a href="/">C Z</a></h1>
		</hgroup>

		
		<p class="header-subtitle">双能卫</p>
		

		
			<div class="switch-btn">
				<div class="icon">
					<div class="icon-ctn">
						<div class="icon-wrap icon-house" data-idx="0">
							<div class="birdhouse"></div>
							<div class="birdhouse_holes"></div>
						</div>
						<div class="icon-wrap icon-ribbon hide" data-idx="1">
							<div class="ribbon"></div>
						</div>
						
						
					</div>
					
				</div>
				<div class="tips-box hide">
					<div class="tips-arrow"></div>
					<ul class="tips-inner">
						<li>菜单</li>
						<li>标签</li>
						
						
					</ul>
				</div>
			</div>
		

		<div class="switch-area">
			<div class="switch-wrap">
				<section class="switch-part switch-part1">
					<nav class="header-menu">
						<ul>
						
							<li><a href="/blog">主页</a></li>
				        
							<li><a href="/blog/archives">所有文章</a></li>
				        
						</ul>
					</nav>
					<nav class="header-nav">
						<div class="social">
							
								<a class="github" target="_blank" href="http://github.com/cz-it" title="github">github</a>
					        
						</div>
					</nav>
				</section>
				
				
				<section class="switch-part switch-part2">
					<div class="widget tagcloud" id="js-tagcloud">
						<a href="/blog/tags/backend-vmstat/" style="font-size: 10px;">backend vmstat</a> <a href="/blog/tags/golang-cli/" style="font-size: 10px;">golang cli</a> <a href="/blog/tags/navigation/" style="font-size: 10px;">navigation</a> <a href="/blog/tags/swift/" style="font-size: 10px;">swift</a> <a href="/blog/tags/tabbar/" style="font-size: 10px;">tabbar</a>
					</div>
				</section>
				
				
				

				
			</div>
		</div>
	</header>				
</div>
    </div>
    <div class="mid-col">
      <nav id="mobile-nav">
  	<div class="overlay">
  		<div class="slider-trigger"></div>
  		<h1 class="header-author js-mobile-header hide">C Z</h1>
  	</div>
	<div class="intrude-less">
		<header id="header" class="inner">
			<div class="profilepic">
				<img lazy-src="http://images.libcz.com:8000/images/white_bg_logo.png" class="js-avatar">
			</div>
			<hgroup>
			  <h1 class="header-author">C Z</h1>
			</hgroup>
			
			<p class="header-subtitle">双能卫</p>
			
			<nav class="header-menu">
				<ul>
				
					<li><a href="/blog">主页</a></li>
		        
					<li><a href="/blog/archives">所有文章</a></li>
		        
		        <div class="clearfix"></div>
				</ul>
			</nav>
			<nav class="header-nav">
				<div class="social">
					
						<a class="github" target="_blank" href="http://github.com/cz-it" title="github">github</a>
			        
				</div>
			</nav>
		</header>				
	</div>
</nav>
      <div class="body-wrap"><article id="post-golang/standard_library/flag/golang实现命令行菜单cli效果" class="article article-type-post" itemscope itemprop="blogPost">
  
    <div class="article-meta">
      <a href="/blog/2015/03/05/golang/standard_library/flag/golang实现命令行菜单cli效果/" class="article-date">
  	<time datetime="2015-03-04T16:00:00.000Z" itemprop="datePublished">2015-03-05</time>
</a>
    </div>
  
  <div class="article-inner">
    
      <input type="hidden" class="isFancy" />
    
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      Golang实现命令行菜单CLI效果
    </h1>
  

      </header>
      
      <div class="article-info article-info-post">
        
	<div class="article-tag tagcloud">
		<ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog/tags/golang-cli/">golang cli</a></li></ul>
	</div>

        
	<div class="article-category tagcloud">
	<a class="article-category-link" href="/blog/categories/golang/">golang</a>
	</div>


        <div class="clearfix"></div>
      </div>
      
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>在Unix-Like系统中，CLI(Command Line Interface)通常都是通过传统的getopt来实现的，在Python中除了getopt外还有更高级的optparse以及argparse。在Golang中，系统库flag为我们提供了CLI的支持，当然还有一些更高级的封装实现，如<a href="https://github.com/codegangsta/cli" target="_blank" rel="external">codegangsta/cli</a>,<a href="https://github.com/docker/docker/tree/master/pkg/mflag" target="_blank" rel="external">docker增强的flag</a>。但是对于平民百姓的一般使用，用官方的flag库即可。</p>
<p>##初试<br>使用Flat模块，通过几行代码就可以构建一个丰富的CLI界面了：</p>
<pre><code>package main

<span class="keyword">import</span> (
    <span class="string">"flag"</span>
    <span class="string">"fmt"</span>
)

<span class="func"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {

    flag.<span class="type">Usage</span> = <span class="func"><span class="keyword">func</span><span class="params">()</span></span> {
        fmt.<span class="type">Println</span>(<span class="string">"Usage:Test Flag Module"</span>)
        flag.<span class="type">PrintDefaults</span>()
    }

    <span class="keyword">var</span> logSwitch bool
    <span class="keyword">var</span> outputPath string

    flag.<span class="type">BoolVar</span>(&amp;logSwitch, <span class="string">"l"</span>, <span class="literal">false</span>, <span class="string">"    Logcat switch "</span>)
    flag.<span class="type">StringVar</span>(&amp;outputPath, <span class="string">"out"</span>,<span class="string">"./output"</span>,<span class="string">"     Path to output"</span>)
    <span class="keyword">var</span> v = flag.<span class="type">Bool</span>( <span class="string">"v"</span>, <span class="literal">true</span>, <span class="string">"    Show Version"</span>)

    flag.<span class="type">Parse</span>()

    fmt.<span class="type">Println</span>(<span class="string">"v is "</span>,*v)
    <span class="keyword">for</span> i,<span class="number">_</span>  := range flag.<span class="type">Args</span>() {
        fmt.<span class="type">Println</span>(<span class="string">"the "</span>,i,<span class="string">" th arg is:"</span>,flag.<span class="type">Arg</span>(i))
    }

}
</code></pre><p>执行 <code>go build</code> 并运行程序后，可以尝试几个选项，比如：</p>
<pre><code>./test_flag -h
Usage:Test Flag Module
     -<span class="ruby">l=<span class="symbol">false:</span> <span class="constant">Logcat</span> switch
</span>     -<span class="ruby">out=<span class="string">"./output"</span><span class="symbol">:</span> <span class="constant">Path</span> to output
</span>  -<span class="ruby">v=<span class="symbol">true:</span> <span class="constant">Show</span> <span class="constant">Version</span></span>
</code></pre><p>这里可以使用</p>
<ul>
<li>带有选项和值的参数，如： <code>./test_flag -l false</code> </li>
<li>带有两个横杠的，如： <code>./test_flag --out ./output_path</code></li>
<li>单个参数的选项，如：<code>./test_flag -v</code></li>
<li>带有多个参数，如：<code>./test_flag one two</code></li>
</ul>
<p>在上面的例子中，通过调用<code>flag.Parse()</code>对选项进行了解析，从而可以获得选项字段。而选项字段内容的获取则通过<br><code>Type[Var]()</code>的接口获得，其中参数忽略第一个分别是</p>
<ul>
<li>命令行上给到的名称</li>
<li>默认值</li>
<li>help提示信息</li>
</ul>
<p>主要提供了Bool,Duration,Float64,Int,UInt,Int64,UInt64,String几种类型数据的获得，每个类型有一个带有Var后缀的版本，该版本会再前面多<br>增加一个参数，为对应类型的指针，用来存储获得的值，而不带的版本则是返回一个该类型的指针，从而获得对应选项的值。如：</p>
<pre><code><span class="function">func <span class="title">Int</span>(<span class="params">name <span class="keyword">string</span>, <span class="keyword">value</span> <span class="keyword">int</span>, usage <span class="keyword">string</span></span>) *<span class="keyword">int</span>
func <span class="title">IntVar</span>(<span class="params">p *<span class="keyword">int</span>, name <span class="keyword">string</span>, <span class="keyword">value</span> <span class="keyword">int</span>, usage <span class="keyword">string</span></span>)</span>
</code></pre><p>分别是通过返回值和指定变量指针的方式。</p>
<p>在写Python的命令行时，有时候为了兼容多个Python版本，通常选择最裸的argv的方式，这样通常都免不了对参数进行转换的操作，如:</p>
<pre><code>try:
    count = <span class="function"><span class="title">int</span><span class="params">(sys.argv[<span class="number">1</span>])</span></span>
except:
    <span class="function"><span class="title">echo_helper</span><span class="params">()</span></span>
</code></pre><p>而上面的这些带有类型的接口就已经完成了这样的操作，比如传入一个浮点数给到一个Int<a href="">Var</a>的选项，将会自动打印出Help信息并进行退出动作。</p>
<p>上面我们看到通过为<code>flag.Usage</code> 进行赋值，可以实现自定义的help输出。如果使用了上面的demo可能会觉得奇怪，为什么没有对各个选项写help信息<br>为什么默认的help会打印出对应的信息。这个神奇的语法就得依赖于 <code>flag.PrintDefaults()</code>，他会把其他选项中的help一并打印出来。<br>另外在写单个项的help的时候，字符串最好tab一个键开头，这样<code>flag.PrintDefaults()</code>在合成help信息的时候，排版起来相对好看。</p>
<p>除了选项外，还可以获取没有选项带的参数值，根据上面的经验，这些值也是需要Parse之后才能得到，因此对其的遍历放在了Parse操作之后，否则<br>取出来的都是空。</p>
<p>上面的这个Demo基本可以作为一个模板，稍加修改便可以满足一个简单的CLI的需求。下面对Unix-Like命令中几种常见选项做一个实现。</p>
<p>##-v/–verion</p>
<p>对于单个选项的开关，需要将该选项定义为bool类型，并默认给一个false的值。这样可以实现当有该选项时，会被设置为true，否则任为false。</p>
<pre><code><span class="tag">var</span> v = flag.<span class="function"><span class="title">Bool</span><span class="params">( <span class="string">"v"</span>, false, <span class="string">"    Show Version"</span>)</span></span>

flag.<span class="function"><span class="title">Parse</span><span class="params">()</span></span>
fmt.<span class="function"><span class="title">Println</span><span class="params">(<span class="string">"v is "</span>,*v)</span></span>
</code></pre><p>这样在没有加 “-v”的时候得到的结果为false,加了的话，得到的是true。</p>
<p>flag比较讨厌的是，对于长短选项没有做特殊的合并方法。感觉只能二者选其一了。</p>
<p>##-o a.out</p>
<p>如果想获取一个带有参数的选项的值，可以选择的使用上面的Type<a href="">Var</a>系列的函数了，这里以String举例：</p>
<pre><code><span class="tag">var</span> outputName string
<span class="tag">var</span> outputDir = flag.<span class="function"><span class="title">String</span><span class="params">(<span class="string">"o"</span>,<span class="string">""</span>,<span class="string">"    output directory"</span>)</span></span>
flag.<span class="function"><span class="title">StringVar</span><span class="params">(&amp;outputName,<span class="string">"n"</span>,<span class="string">""</span>, <span class="string">"    output name"</span>)</span></span>
flag.<span class="function"><span class="title">Parse</span><span class="params">()</span></span>
fmt.<span class="function"><span class="title">Println</span><span class="params">(<span class="string">"Output directory is "</span>,*outputDir,<span class="string">" And name is "</span>,outputName)</span></span>
</code></pre><p>这里通过带有Var和不带的接口实现了获取输出目录和文件名的操作。</p>
<p>##mkdir tmp<br>如果不想使用选项，而直接带上参数，flag也是支持的，可以通过flag.Args()获得一个sting的list，或者通过<br>flag.Arg(index int)获得第几个参数。但是这里受限的时，参数只能获得string类型，具体需要自己转换一下，就和Python里面<br>使用getopt一样。<br>      flag.Parse()</p>
<pre><code><span class="keyword">if</span> len(flag.Args())  !=<span class="number">2</span> {
    flag.Usage()
    <span class="keyword">return</span>
}

argName := flag.Arg(0)
argAge := flag.Arg(1)
age,err := strconv.Atoi(argAge)
<span class="keyword">if</span> err != nil {
    flag.Usage()
    <span class="keyword">return</span>
}

fmt.Printf(<span class="string">"Name is %s,Age is %d\n"</span>,argName,age)
</code></pre><p>这里先对参数个数进行判断，然后再用strconv来实现对字符串的转换操作。如果转换不成功则退出。</p>
<p>##Help</p>
<p>上面已经对Help进行了比较详尽的说明了，总结就是在适当的时候会调用flag.Usage。其是一个无参数不返回值的函数，相当于<br>C里面的<code>void (* func)()</code>。其配合一个助手<code>flag.PrintDefaults()</code>可以将选项中得help信息也打印出来。基本就是一个<br>范式，每次这么写就对了：</p>
<pre><code>flag.Usage = <span class="function"><span class="keyword">func</span><span class="params">()</span> {</span>
    fmt.Println(<span class="string">"Usage:Test Flag Module"</span>)
    flag.PrintDefaults()
}
</code></pre>
      
    </div>
    
  </div>
  
    
<nav id="article-nav">
  
    <a href="/blog/2015/04/04/swift/swift_继承中构造函数/继承中的构造函数/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption"><</strong>
      <div class="article-nav-title">
        
          Swift语法：继承中的构造函数
        
      </div>
    </a>
  
  
</nav>

  
</article>


<div class="share">
	<!-- JiaThis Button BEGIN -->
	<div class="jiathis_style">
		<span class="jiathis_txt">分享到：</span>
		<a class="jiathis_button_tsina"></a>
		<a class="jiathis_button_cqq"></a>
		<a class="jiathis_button_douban"></a>
		<a class="jiathis_button_weixin"></a>
		<a class="jiathis_button_tumblr"></a>
		<a href="http://www.jiathis.com/share" class="jiathis jiathis_txt jtico jtico_jiathis" target="_blank"></a>
	</div>
	<script type="text/javascript" src="http://v3.jiathis.com/code/jia.js?uid=1405949716054953" charset="utf-8"></script>
	<!-- JiaThis Button END -->
</div>



<div class="duoshuo">
	<!-- 多说评论框 start -->
	<div class="ds-thread" data-thread-key="golang/standard_library/flag/golang实现命令行菜单cli效果" data-title="Golang实现命令行菜单CLI效果" data-url="http://www.libcz.com/blog/blog/2015/03/05/golang/standard_library/flag/golang实现命令行菜单cli效果/"></div>
	<!-- 多说评论框 end -->
	<!-- 多说公共JS代码 start (一个网页只需插入一次) -->
	<script type="text/javascript">
	var duoshuoQuery = {short_name:"true"};
	(function() {
		var ds = document.createElement('script');
		ds.type = 'text/javascript';ds.async = true;
		ds.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js';
		ds.charset = 'UTF-8';
		(document.getElementsByTagName('head')[0] 
		 || document.getElementsByTagName('body')[0]).appendChild(ds);
	})();
	</script>
	<!-- 多说公共JS代码 end -->
</div>




</div>
      <footer id="footer">
  <div class="outer">
    <div id="footer-info">
      <p><a rel="license" href="http://creativecommons.org/licenses/by-nd/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nd/4.0/88x31.png" /></a><br />本站采用<a rel="license" href="http://creativecommons.org/licenses/by-nd/4.0/">知识共享Attribution-NoDerivatives 4.0 国际许可协议</a>进行许可。</p>
    	<div class="footer-left">
    		&copy; 2015 C Z
    	</div>
      	<div class="footer-right">
      		<a href="http://hexo.io/" target="_blank">Hexo</a>  Theme <a href="https://github.com/litten/hexo-theme-yilia" target="_blank">Yilia</a> by Litten
      	</div>
    </div>
  </div>
</footer>

    </div>
    
  <link rel="stylesheet" href="/blog/fancybox/jquery.fancybox.css" type="text/css">


<script>
	var yiliaConfig = {
		fancybox: true,
		mathjax: true,
		animate: true,
		isHome: false,
		isPost: true,
		isArchive: false,
		isTag: false,
		isCategory: false,
		open_in_new: false
	}
</script>
<script src="http://7.url.cn/edu/jslib/comb/require-2.1.6,jquery-1.9.1.min.js" type="text/javascript"></script>
<script src="/blog/js/main.js" type="text/javascript"></script>






<script type="text/x-mathjax-config">
MathJax.Hub.Config({
    tex2jax: {
        inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
        processEscapes: true,
        skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
    }
});

MathJax.Hub.Queue(function() {
    var all = MathJax.Hub.getAllJax(), i;
    for(i=0; i < all.length; i += 1) {
        all[i].SourceElement().parentNode.className += ' has-jax';                 
    }       
});
</script>

<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>


  </div>
</body>
</html>